﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Data;
using System.Globalization;
using System.Collections.Specialized;
using System.Collections;

namespace SystemX
{
	public static partial class ObjectX
	{
		/// <summary>
		/// BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance
		/// </summary>
		public static BindingFlags DefaultBindingFlags {
			get
			{
				return   BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
			}
		}
		/// <summary>
		/// BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly
		/// </summary>
		public static BindingFlags ExcludeBaseBindingFlags {
			get
			{
				return BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
			}
		}
		
		#region Properties resolving
		/// <summary>
		/// Get All properties
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public static List<string> GetProperties(this object value)
		{
			return value.GetType().GetProperties1(DefaultBindingFlags);
		}
		public static List<string> GetProperties(this object value, BindingFlags flags)
		{
			return value.GetType().GetProperties1(flags);
		}
		public static List<string> GetProperties1(this Type t,BindingFlags flags) {
			return t.GetProperties(flags).Select(z=>z.Name).ToList();
		}
		/// <summary>
		/// Get a property's value.
		/// </summary>
		/// <param name="value"></param>
		/// <param name="propertyName"></param>
		/// <returns></returns>
		public static object GetPropertyValue(this object value, string propertyName)
		{
			PropertyInfo propertyInfo = value.GetType().GetProperty(propertyName);
			return propertyInfo.GetValue(value, null);
		}
		/// <summary>
		/// Get the value of the specified property,which has the specified custom attribute.
		/// </summary>
		/// <param name="value"></param>
		/// <param name="attriType"></param>
		/// <returns></returns>
		public static object GetPropertyValue(this object value, Type attributeType)
		{
			object retVal = null;
			var t = value.GetType();
			var p = t.GetProperties().ToList()
				.Where(x => x.IsDefined(attributeType, false));
			if (p != null && p.Count() > 0)
			{
				var tempP = p.ToList()[0];
				retVal = value.GetPropertyValue(tempP.Name);
			}
			return retVal;
		}
		/// <summary>
		/// Get the values of the specified property,which has the specified custom attribute.
		/// </summary>
		/// <param name="value"></param>
		/// <param name="attributeType">if null,search for all properties</param>
		/// <returns></returns>
		public static Dictionary<string, object> GetPropertyValues(this object value, Type attributeType)
		{
			return GetPropertyValues(value, attributeType, true);
		}
		/// <summary>
		/// Get the values of the specified property,which has the specified custom attribute.
		/// </summary>
		/// <param name="value"></param>
		/// <param name="attributeType">if null,search for all properties</param>
		/// <param name="include">include the specified custom attribute</param>
		/// <returns></returns>
		public static Dictionary<string, object> GetPropertyValues(this object value, Type attributeType, bool include)
		{
			return GetPropertyValues(value, attributeType, include, DefaultBindingFlags);
		}
		/// <summary>
		/// Get the values of the specified property,which has the specified custom attribute.
		/// </summary>
		/// <param name="value"></param>
		/// <param name="attributeType">if null,search for all properties</param>
		/// <param name="include">include the specified custom attribute</param>
		/// <returns></returns>
		public static Dictionary<string,object> GetPropertyValues(this object value, Type attributeType, bool include,BindingFlags searchFlags)
		{
			var retVal = new Dictionary<string, object>();

			var t = value.GetType();
			var p = t.GetProperties(searchFlags).ToList();
			if (attributeType != null)
			{
				p = p
					.Where(x =>
					{
						if (include)
						{
							return x.IsDefined(attributeType, false);
						};
						return !x.IsDefined(attributeType, false);
					}).ToList();
			}
			if (p != null && p.Count > 0)
			{
				object tempVal = null;
				p = p.OrderBy(z => z.Name).ToList();
				p.ForEach(x =>
				{
					try
					{
						tempVal = value.GetPropertyValue(x.Name);
					}
					catch (Exception ex) {
						tempVal =ex.Message;
					}
					retVal.Add(x.Name,tempVal);
				});
			}
			return retVal;
		}
        public static bool SetPropertyValue(this object obj, string propertyName,object val, bool ignoreCase) {
            var retVal = false;
            var canSet = false;
            obj.GetType().GetProperties().ToList().ForEach(x => {
                canSet = x.CanWrite && (ignoreCase ? x.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase) : (x.Name == propertyName));
                if (canSet) {
                    try
                    {
						x.SetValue(obj, val, null);
                        retVal = true;
                    }
                    catch (Exception ex) { 
                        //do nothing...,maybe we can log the exception!
						try
						{
							x.SetValue(obj, Convert.ChangeType(val, x.PropertyType, CultureInfo.CurrentCulture), null);
							retVal = true;
						}
						catch (Exception ex1) { 
							//...
						}
                    }
                }
            });
            return retVal;
        }
        /// <summary>
        /// Full fill an obj using a StringDictionary.Use this method to read xml settings to a strong type object 
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="settings"></param>
        /// <param name="ignoreCase"></param>
        public static void SetPropertyValues(this object obj,StringDictionary settings,bool ignoreCase) {
            foreach (DictionaryEntry e in settings) {
                obj.SetPropertyValue(e.Key.ToString(), e.Value, ignoreCase);
            }
        }
		#endregion

		public static DataTable ToDataTable<T>(this IEnumerable<T> items, params string[] fields) {
			if (fields == null || fields.Length == 0) {
				return ToDataTable<T>(items, false, null);
			}
			var t = typeof(T);
			DataTable dt = new DataTable(t.Name);


			fields.ToList().ForEach(x =>
			{
				using (DataColumn col = new DataColumn(x))
				{
					dt.Columns.Add(col);
				}
			});

			DataRow rw;
			items.ToList().ForEach(x =>
			{
				rw = dt.NewRow();
				fields.ToList().ForEach(y =>
				{
					rw[y] = x.GetPropertyValue(y);
				});
				dt.Rows.Add(rw);
			});

			return dt;
		}
		public static DataTable ToDataTable<T>(this IEnumerable<T> items, bool includeBaseProperties, params string[] excludeFields)
		{
			var t = typeof(T);
			DataTable dt = new DataTable(t.Name);
			List<string> propertyNameList = new List<string>();
			if (includeBaseProperties)
			{
				propertyNameList = t.GetProperties1(DefaultBindingFlags);
			}
			else
			{
				propertyNameList = t.GetProperties1(ExcludeBaseBindingFlags);
			};

			if (null != excludeFields && excludeFields.Length > 0)
			{
				propertyNameList = propertyNameList.Where(x=>!excludeFields.Contains(x)).ToList();
			};

			propertyNameList.ForEach(x =>
			{
				using (DataColumn col = new DataColumn(x))
				{
					dt.Columns.Add(col);
				}
			});

			DataRow rw;
			items.ToList().ForEach(x =>
			{
				rw = dt.NewRow();
				propertyNameList.ForEach(y =>
				{
					rw[y] = x.GetPropertyValue(y);
				});
				dt.Rows.Add(rw);
			});

			return dt;
		}
	}
}
